package com.cloudfast.oauth.granter;

import java.util.LinkedHashMap;
import java.util.Map;

import org.bouncycastle.crypto.InvalidCipherTextException;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import com.cloudfast.common.sm2.Sm2Encryptor;
import com.cloudfast.context.CloudSecurity;
import com.cloudfast.context.error.CloudException;
import com.cloudfast.context.error.ErrorCode;
import com.cloudfast.redis.CloudRedisTemplate;

/**
 * 验证码验证登入
 *
 * @author liuyw
 * @date 2022年9月1日
 */
public class CaptchaTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "captcha";

    /**
     * 密码错误次数
     */
    private static final int errorTotalCount = 5;

    /**
     * 账号锁定时间 默认10分钟
     */
    private static final int passwordErrorLockTime = 10;
    /**
     * 查询用户信息
     */
    private UserDetailsService userDetailsService;

    /**
     *
     */
    private CloudRedisTemplate redisTemplate;

    /**
     * 密码对比
     */
    private BCryptPasswordEncoder passwordEncoder;

    /**
     * 系统安全
     */
    private CloudSecurity cloudSecurity;

    /**
     * 解密
     */
    Sm2Encryptor sm2Encryptor = new Sm2Encryptor();

    public CaptchaTokenGranter(AuthenticationManager authenticationManager,
                               /**
                                * 默认tokenServices
                                */
                               AuthorizationServerTokenServices tokenServices,
                               /**
                                * 默认客户端services
                                */
                               ClientDetailsService clientDetailsService,

                               /**
                                * requestFactory
                                */
                               OAuth2RequestFactory requestFactory,

                               /**
                                * redisTemplate
                                */
                               CloudRedisTemplate redisTemplate,

                               /**
                                * userDetailsService
                                */
                               UserDetailsService userDetailsService,

                               /**
                                * passwordEncoder
                                */
                               BCryptPasswordEncoder passwordEncoder,

                               /**
                                * 系统安全配置
                                */
                               CloudSecurity cloudSecurity) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.redisTemplate = redisTemplate;
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
        this.cloudSecurity = cloudSecurity;
    }

    protected CaptchaTokenGranter(AuthenticationManager authenticationManager,
                                  AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
                                  OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());

        /**
         * 用户名
         */
        String username = parameters.get("username");

        /**
         * 密码
         */
        String password = parameters.get("password");

        if (cloudSecurity.isCaptcha()) {
            /**
             * 验证码
             */
            String captcha = parameters.get("captcha");

            /**
             * sessionId
             */
            String sessionId = parameters.get("sessionId");
            /**
             * redis 中的 验证码
             */
            String redisCaptcha = redisTemplate.get(sessionId) + "";
            // 判断是否相等
            if (!captcha.equalsIgnoreCase(redisCaptcha)) {
                // 不相等 抛出异常
                throw new CloudException(ErrorCode.CAPTCHA_CODE_ERROR);
            }
        }

        try {
            int errorCount = 0;
            Object errorCountRedis = redisTemplate.get(username);
            if (errorCountRedis != null) {
                errorCount = Integer.parseInt(errorCountRedis + "");
            }
            if (errorCount < errorTotalCount) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                // 查询用户及查询信息
                if (userDetails != null) {
                    String inputPassword = sm2Encryptor.decryptString(cloudSecurity.getPrivateKey(), password);

                    /**
                     * 对比密码
                     */
                    if (passwordEncoder.matches(inputPassword, userDetails.getPassword())) {
                        redisTemplate.set(userDetails.getUsername(), 0, 60 * passwordErrorLockTime);
                        Authentication userAuth = new UsernamePasswordAuthenticationToken(userDetails,
                                userDetails.getPassword(), userDetails.getAuthorities());
                        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
                        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
                        return new OAuth2Authentication(storedOAuth2Request, userAuth);
                    } else {
                        errorCount = errorCount + 1;
                        redisTemplate.set(userDetails.getUsername(), errorCount, 60 * passwordErrorLockTime);
                        errorCount = errorTotalCount - errorCount;
                        if (errorCount > 0) {
                            throw new CloudException(ErrorCode.PASSWORD_ERROR_COUNT, (errorCount) + "");
                        } else {
                            throw new CloudException(ErrorCode.PASSWORD_ERROR_LOCK);/**/
                        }
                    }
                } else {
                    throw new CloudException(ErrorCode.PASSWORD_PARSE_ERROR);
                }
            } else {
                throw new CloudException(ErrorCode.PASSWORD_ERROR_LOCK);
            }

        } catch (CloudException e) {
            throw new CloudException(e.getErrorCode());
        } catch (InvalidCipherTextException e) {
            throw new CloudException(ErrorCode.PASSWORD_PARSE_ERROR);
        } catch (NumberFormatException e) {
            throw new CloudException(ErrorCode.PASSWORD_PARSE_ERROR);
        }
    }

}
